#include "playthread.h"
#include <SDL2/SDL.h>
#include <QFile>

/* 一些宏定义 */
// 采样率
#define SAMPLE_RATE 44100
// 采样格式
#define SAMPLE_FORMAT AUDIO_S16LSB
// 采样大小
#define SAMPLE_SIZE SDL_AUDIO_BITSIZE(SAMPLE_FORMAT)
// 声道数
#define CHANNELS 2
// 音频缓冲区的样本数量
#define SAMPLES 1024

#ifdef Q_OS_WIN
    #define FILENAME "C:/Users/zuo/Desktop/123/in_10.pcm"
#else
    #define FILENAME "/Users/zuojie/Desktop/in.pcm"
#endif

// 每个样本占用多少个字节
#define BYTES_PER_SAMPLE ((SAMPLE_SIZE * CHANNELS) >> 3)
// 文件缓冲区的大小，例如：4096
#define BUFFER_SIZE (SAMPLES * BYTES_PER_SAMPLE)

// 用于存储读取的音频数据和长度
typedef struct{
    int len;
    int pullLen = 0;//缓存区每次拉取的数据
    Uint8 * data = nullptr;
}AudioBuffer;

PlayThread::PlayThread(QObject *parent) : QThread(parent){
    connect(this,&PlayThread::finished,&PlayThread::deleteLater);
}

PlayThread::~PlayThread(){
    disconnect();
    requestInterruption();
    quit();
    wait();
    qDebug() << this << "析构了";
}

//int bufferLen;
//char *bufferData;

/**
 * @brief pullAudioData
 * @param userdata SDL_AudioSpec.userdata
 * @param stream 音频缓冲区（需要将音频数据填充到这个缓冲区）
 * @param len 音频缓冲区的大小（SDL_AudioSpec.samples * 每个样本的大小）
 */
void pullAudioData(void *userdata,
                   // 需要往stream中填充PCM数据
                   Uint8 * stream,
                   // 希望填充的大小(samples * format * channels / 8)
                   int len){
    // 清空stream
    SDL_memset(stream, 0, len);

    // 取出缓冲信息
    AudioBuffer *buffer = (AudioBuffer *) userdata;
    // 文件数据还没准备好
    if (buffer->len <= 0) return;

    // 取len、bufferLen的最小值（为了保证数据安全，防止指针越界）
    buffer->pullLen = (len > buffer->len) ? buffer->len : len;

    // 填充数据
    SDL_MixAudio(stream,buffer->data,buffer->pullLen,SDL_MIX_MAXVOLUME);
    buffer->data += buffer->pullLen;
    buffer->len -= buffer->pullLen;
}

/*
SDL播放音频有2种模式：
Push（推）：【程序】主动推送数据给【音频设备】
Pull（拉）：【音频设备】主动向【程序】拉取数据 -- 采用这种
*/
void PlayThread::run(){
    // 初始化Audio子系统
    if(SDL_Init(SDL_INIT_AUDIO) < 0){
        SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
        return;
    }

    // 音频参数
    SDL_AudioSpec spec;
    // 采样率
    spec.freq = SAMPLE_RATE;
    // 声道数
    spec.channels = CHANNELS;
    // 采样格式（s16le）
    spec.format = SAMPLE_FORMAT;
    // 音频缓冲区的样本数量（这个值必须是2的幂）
    spec.samples = SAMPLES;
    // 回调
    spec.callback = pullAudioData;

    // 传递给回调的参数
    AudioBuffer buffer;
    spec.userdata = &buffer;

    // 打开音频设备
    if (SDL_OpenAudio(&spec, nullptr)) {
        qDebug() << "SDL_OpenAudio Error" << SDL_GetError();
        // 清除所有初始化的子系统
        SDL_Quit();
        return;
    }

    QFile file(FILENAME);
    if(!file.open(QFile::ReadOnly)){
        qDebug() << "文件打开失败" << FILENAME;
        // 关闭音频设备
        SDL_CloseAudio();
        // 清除所有初始化的子系统
        SDL_Quit();
        return;
    }

    // 开始播放(0是取消暂停)
    SDL_PauseAudio(0);

    // 存放从文件中读取的数据
    char data[BUFFER_SIZE];
    while (!isInterruptionRequested()) {
        // 只要从文件中读取的音频数据，还没有填充完毕，就跳过
        if(buffer.len > 0) continue;
        buffer.len = file.read(data,BUFFER_SIZE);

        /*
         * SDL_Delay(剩余时间);
         *
         * 采样率(每秒采样的样本次数)用SAMPLE_RATE表示,
         * 每个样本的大小，用BYTES_PER_SAMPLE表示,
         *
         * 剩余的样本数量 = buffer.pullLen / BYTES_PER_SAMPLE,
         * 剩余时间 = 剩余的样本数量 / 采样率
        */
        // 文件数据已经读取完毕
        if (buffer.len <= 0){
            // 下面三句代码作用是：推迟线程结束的时间,否则在音频快播放结束时会出现突然停止
            // 剩余的样本数量
            int samples = buffer.pullLen / BYTES_PER_SAMPLE;
            int ms = samples * 1000 / SAMPLE_RATE;
            SDL_Delay(ms);
//            SDL_Log("buffer.pullLen:%d,BYTES_PER_SAMPLE:%d,samples:%d,ms:%d",buffer.pullLen,BYTES_PER_SAMPLE,samples,ms);
            break;
        }

        // 读取到了文件数据
        buffer.data = (Uint8 *)data;
    }
    /*while (!isInterruptionRequested()) {
        bufferLen = file.read(data,BUFFER_SIZE);

        // 文件数据已经读取完毕
        if (bufferLen <= 0) break;
        // 读取到了文件数据
        bufferData = data;

        // 等待音频数据填充完毕
        // 只要音频数据还没有填充完毕，就Delay(sleep)
        while (bufferLen > 0) {
            // SDL_Delay(1);// 睡眠1毫秒,可以不用调用SDL_Delay，while空转也可以
        }
    }*/

    // 关闭文件
    file.close();
    // 关闭音频设备
    SDL_CloseAudio();
    // 清除所有初始化的子系统
    SDL_Quit();
}
